package org.sgx.dao;

import java.util.Collection;
import java.util.Date;

import org.sgx.dao.Dao.ModelAccessor;
import org.sgx.dao.model.Model;
import org.sgx.dao.util.BeanUtil;
import org.sgx.dao.util.OrderedMap;
import org.sgx.dao.util.SQLUtil;


/**
 * This class is a high level utility for persisting Java Beans. Only those bean properties 
 * of supported types will be persisted, while properties of not supported types will be ignored silently. 
 * 
 * <p>Supported types are defined at method getSQLTypeFor() and are: </p>
 * String, Long , Short, Integer, Boolean, Date, byte[] 
 * 
 * @author sg 
 * @param <T>
 */
public class BeanDao<T extends Model> extends AbstractDao<T> {

	String tableName;
	private T modelInstanceExample;
	
	public BeanDao(ConnectionGiver connectionGiver, Class<T> modelClass, 
			String tableName_, OrderedMap<String, String>sqlColumns_, 
			ModelAccessor<T> modelAccessor_) throws DaoException {
		super(connectionGiver);
		this.tableName=tableName_==null?buildClassNameFor(modelClass):tableName_;
		this.modelClass=modelClass;
		this.modelInstanceExample = instantiateModel();
		
		if(sqlColumns_!=null) {
			this.sqlColumns=sqlColumns_;
		}
		else {
			this.sqlColumns=new OrderedMap<String, String>();
			Collection<String> props = BeanUtil.getProperties(modelClass);
			for(String p : props) {
				String type = getSQLTypeFor(p);
				if(type!=null)
					this.sqlColumns.put(p, type);
			}
		}
		if(modelAccessor_!=null) {
			this.modelAccessor=modelAccessor_;
		}
		else {
			this.modelAccessor = new ModelAccessor<T>() {
				public Object get(T model, String name) {
					try {
						return BeanUtil.getProperty(model, name);
					} catch (Exception e) {
						return null;
					}				
				}
				public void set(T model, String name, Object value) {
					try {
						BeanUtil.setProperty(model, name, value);
					} catch (Exception e) {
					}				
				}			
			};
		}
		
		//now that we provide all information for the abstract dao, we proceed to create the sql table if not exists, and we are ready to work
		createTable();		
	}
	
	public BeanDao(ConnectionGiver connectionGiver, Class<T> modelClass) throws DaoException {
		this(connectionGiver, modelClass, null, null, null);
	}

	public BeanDao(ConnectionGiver connectionGiver, Class<T> modelClass, String tableName) throws DaoException {
		this(connectionGiver, modelClass, tableName, null, null);
	}
	
	private String buildClassNameFor(Class<?> c) throws DaoException {
		return "_class"+getClassUniqueKey(c);
	}

	private static byte[] _byteArrayExample = new byte[0]; 
	protected String getSQLTypeFor(String fieldName) throws DaoException {
		Class<?> fieldClass = BeanUtil.getPropertyType(modelClass, fieldName);
		if(fieldClass.equals(String.class)) {
			return SQLUtil.TYPE_STRING;
		}
		else if(fieldClass.equals(Long.class)) {
			return SQLUtil.TYPE_LONG;
		}
		else if(fieldClass.equals(Short.class)) {
			return SQLUtil.TYPE_SHORT;
		}
		else if(fieldClass.equals(Integer.class)) {
			return SQLUtil.TYPE_INTEGER;
		}
		else if(fieldClass.equals(Boolean.class)) {
			return SQLUtil.TYPE_BOOLEAN;
		}
		else if(fieldClass.equals(Date.class)) {
			return SQLUtil.TYPE_DATETIME;
		}
		else if(fieldClass.equals(_byteArrayExample.getClass())) {
			return SQLUtil.TYPE_STREAM;
		}
		else {
			return null;
//			TODO: log the following
//			throw new DaoException("field type not supported for field "+fieldName+" of class "+
//					this.modelClass.getName()+" - not supported java class name: "+fieldClass.getName());
		}
	}
	public String getColumnIdName() {
		return modelInstanceExample.getFieldIdName();//modelidPropertyName;
	}
	public String getModelClassName() {	
		return modelClass.getName();
	}
	public String getTableName() {
		return tableName;
	}

	
	
	
	
	//using map and counter utilities we implement a class unique integer key generator (hashmap for representing a classname with an integer for shorting datatable names.) .

	private static final String GET_CLASS_UNIQUE_KEY_COUNTER_ID = "__getClassUniqueKeyCounter", 
		GET_CLASS_UNIQUE_KEY_STRINGMAP_ID = "__getClassUniqueKeyStringMap";
	/**
	 * this will register if necesary the class with a unique counter and return that counter.
	 */
	public long getClassUniqueKey(Class c) throws DaoException {
		String v = sql.mapGet(GET_CLASS_UNIQUE_KEY_STRINGMAP_ID, c.getName());
		long counter;
		if(v==null) {
			counter = sql.incrementCounter(GET_CLASS_UNIQUE_KEY_COUNTER_ID);
			sql.mapPut(GET_CLASS_UNIQUE_KEY_STRINGMAP_ID, c.getName(), counter+"");
			return counter;
		}
		else {
			return Long.parseLong(v);
		}
	}
}
